﻿/*
(c) 2010 - Open Source Business Foundation e. V. (www.osbf.de)
This software is provided under Microsoft Reciprocal License (Ms-RL)
(http://www.microsoft.com/opensource/licenses.mspx#Ms-RL)
All other rights reserved
*/


using System;
using System.Collections.Generic;
using System.Data.Services;
using System.Data.Services.Common;
using System.Linq;
using System.ServiceModel.Web;
using System.Web;
using MicroFormatProcessing;


/// <summary>
/// This class represents the OData Service for Microformats. 
/// calling Example
/// http://microformats.cloudapp.net/OData/microformats.svc/User('test')/
/// will gather all information about the user test
/// http://microformats.cloudapp.net/OData/microformats.svc/User('test')/Microformats
/// will gather all Microformats of the user test
/// </summary>


namespace MicroFormatWebRole.OData {
    using System;
    using System.Collections.Generic;
    using System.Configuration;
    using System.Data.Services;
    using System.Data.Services.Common;
    using System.Data.Services.Providers;
    using System.Diagnostics;
    using System.Linq;
    using System.ServiceModel;
    using System.ServiceModel.Web;
    using System.Web;
    using DataServiceProvider;

    [JSONPSupportBehavior]
    [ServiceBehavior(IncludeExceptionDetailInFaults = true)]
    public class MicroFormatsService : DSPDataService<DSPContext> {
        /// <summary>
        /// Maximum number of entities one can add to a container
        /// </summary>
        public const int MaxEntityPerContainer = 50;

        /// <summary>
        /// Maximum length of any string property
        /// </summary>
        public const int MaxStringPropertyLength = 256;

        /// <summary>
        /// Maximum number of items cached per session
        /// </summary>
        public const int MaxCachedSessionItems = 5000;

        /// <summary>
        /// The Session-Data cache
        /// </summary>
        private static Dictionary<String, DSPContext> _dsCache = new Dictionary<string, DSPContext>(MaxCachedSessionItems);

        /// <summary>
        /// The static metadata object
        /// </summary>
        private static DSPMetadata _metadata;

        #region Metadata and Data Generation

        /// <summary>
        /// Generates the default metadata for the service
        /// </summary>
        private static void CreateDefaultMetadata() {
            _metadata = new DSPMetadata(Constants.ODATA_CONTAINER_NAME, Constants.ODATA_NAMESPACE_NAME);


            // ENTITY Properties of a Microformat
            ResourceType mfProperty = _metadata.AddEntityType(Constants.ODATA_ENTITY_MICROFORMATPOPERTIES);
            ResourceSet propertySet = _metadata.AddResourceSet(Constants.ODATA_ENTITY_MICROFORMATPOPERTIES, mfProperty);
            _metadata.AddKeyProperty(mfProperty, Constants.ODATA_FIELD_KEY, typeof(string));
            _metadata.AddPrimitiveProperty(mfProperty, Constants.ODATA_FIELD_VALUE, typeof(string));
            _metadata.AddPrimitiveProperty(mfProperty, Constants.ODATA_FEED_KEY, typeof(string));
            _metadata.AddPrimitiveProperty(mfProperty, Constants.ODATA_FEED_VALUE, typeof(string));

            // ENTITY User
            ResourceType user = _metadata.AddEntityType(Constants.ODATA_ENTITY_USER);
            ResourceSet userSet = _metadata.AddResourceSet(Constants.ODATA_ENTITY_USER, user);
            _metadata.AddKeyProperty(user, Constants.ODATA_FIELD_USERID, typeof(string));
            _metadata.AddPrimitiveProperty(user, Constants.ODATA_FEED_USERID, typeof(string));

            // ENTITY Tag 
            ResourceType tag = _metadata.AddEntityType(Constants.ODATA_ENTITY_TAG);
            ResourceSet tagSet = _metadata.AddResourceSet(Constants.ODATA_ENTITY_TAG, tag);
            _metadata.AddKeyProperty(tag, Constants.ODATA_FIELD_TAGNAME, typeof(string));
            _metadata.AddPrimitiveProperty(tag, Constants.ODATA_FIELD_USERID, typeof(string));
            _metadata.AddPrimitiveProperty(tag, Constants.ODATA_FIELD_ROWKEY, typeof(string));
            _metadata.AddPrimitiveProperty(tag, Constants.ODATA_FEED_ROWKEY, typeof(string));


            // ENTITY Microformats 
            ResourceType microformats = _metadata.AddEntityType(Constants.ODATA_ENTITY_MICROFORMAT);
            ResourceSet microfomatsSet = _metadata.AddResourceSet(Constants.ODATA_ENTITY_MICROFORMAT, microformats);
            _metadata.AddKeyProperty(microformats, Constants.ODATA_FIELD_ROWKEY, typeof(string));
            _metadata.AddPrimitiveProperty(microformats, Constants.ODATA_FIELD_USERID, typeof(string));
            _metadata.AddPrimitiveProperty(microformats, Constants.ODATA_FIELD_TYPE, typeof(string));
            _metadata.AddPrimitiveProperty(microformats, Constants.ODATA_FIELD_NAME, typeof(string));
            _metadata.AddPrimitiveProperty(microformats, Constants.ODATA_FIELD_FOUNDONURL, typeof(string));
            _metadata.AddPrimitiveProperty(microformats, Constants.ODATA_FIELD_FOUNDONDATE, typeof(DateTime));
            _metadata.AddPrimitiveProperty(microformats, Constants.ODATA_FIELD_PROPERTYNAMES, typeof(string));
            _metadata.AddPrimitiveProperty(microformats, Constants.ODATA_FIELD_PROPERTYVALUES, typeof(string));
            _metadata.AddPrimitiveProperty(microformats, Constants.ODATA_FIELD_TAGS, typeof(string));
            _metadata.AddPrimitiveProperty(microformats, Constants.ODATA_FEED_ROWKEY, typeof(string));

            // Association
            _metadata.AddNavigationProperty(microformats, Constants.ODATA_ENTITY_USER, false, user, Constants.ODATA_COLLECTIONS_MICROFORMATS, true);
            _metadata.AddNavigationProperty(tag, Constants.ODATA_ENTITY_USER, false, user, Constants.ODATA_COLLECTIONS_TAGS, true);
            _metadata.AddNavigationProperty(microformats, Constants.ODATA_ENTITY_TAG, false, tag, Constants.ODATA_COLLECTIONS_MICROFORMATS, true);
            _metadata.AddNavigationProperty(mfProperty, Constants.ODATA_ENTITY_MICROFORMAT, false, microformats, Constants.ODATA_COLLECTIONS_MICROFORMATPROPERTIES, true);

            // Feed 
            user.AddEntityPropertyMappingAttribute(new EntityPropertyMappingAttribute(Constants.ODATA_FEED_USERID, SyndicationItemProperty.Title, SyndicationTextContentKind.Plaintext, false));
            microformats.AddEntityPropertyMappingAttribute(new EntityPropertyMappingAttribute(Constants.ODATA_FEED_ROWKEY, SyndicationItemProperty.Title, SyndicationTextContentKind.Plaintext, false));
            mfProperty.AddEntityPropertyMappingAttribute(new EntityPropertyMappingAttribute(Constants.ODATA_FEED_KEY, SyndicationItemProperty.Title, SyndicationTextContentKind.Plaintext, false));
            mfProperty.AddEntityPropertyMappingAttribute(new EntityPropertyMappingAttribute(Constants.ODATA_FEED_VALUE, SyndicationItemProperty.Summary, SyndicationTextContentKind.Plaintext, false));
        }

        /// <summary>
        /// Create a DSPResource, and populate it with the given properties
        /// </summary>
        /// <param name="resourceType">The type of the resource</param>
        /// <param name="propertyNames">A list of property names</param>
        /// <param name="values">A list of values for the property names</param>
        /// <returns>A new instance of DSPResource</returns>
        private static DSPResource CreateResourceObject(ResourceType resourceType, string[] propertyNames, params object[] values) {
            Debug.Assert(propertyNames.Length == values.Length, "property and values length does not match");
            DSPResource res = new DSPResource(resourceType);

            for (int i = 0; i < propertyNames.Length; ++i) {
                res.SetValue(propertyNames[i], values[i]);
            }

            return res;
        }

        /// <summary>
        /// Create data for a new session
        /// </summary>
        /// <param name="metadata">The metadata to use</param>
        /// <returns>A new DSPContext containing the default data</returns>
        protected static DSPContext CreateDefaultData(DSPMetadata metadata) {
            DSPContext context = new DSPContext();

            context.SetResourceSetEntities(new MfUserDSPResourceList(context, metadata), Constants.ODATA_ENTITY_USER);
            return context;
        }

        #endregion

        /// <summary>
        /// Initialize the service
        /// </summary>
        /// <remarks>This method is called only once to initialize service-wide policies.</remarks>
        /// <param name="config">The configuration instance</param>
        public static void InitializeService(DataServiceConfiguration config) {
            config.SetEntitySetAccessRule("*", EntitySetRights.All);
            config.SetServiceOperationAccessRule("*", ServiceOperationRights.All);
            config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
            config.UseVerboseErrors = true;
        }



        /// <summary>
        /// Constructor of the service class
        /// </summary>
        /// <remarks>
        /// In here we try to inject the session ID into the URI for all entities
        /// The way we do it is by writing to IncomingMessageProperties["MicrosoftDataServicesRootUri"]
        /// and IncomingMessageProperties["MicrosoftDataServicesRequestUri"].
        /// The first one is the host name used to construct URIs, the second one overrides the request URI
        /// so links gets generated correctly.
        /// The URIs are only generated once per session, and is stored in the session variable.
        /// </remarks>
        public MicroFormatsService() {
            if (HttpContext.Current != null && HttpContext.Current.Session != null &&
                !String.Equals(HttpContext.Current.Session.SessionID, ODataWebSessionIdManager.ODataReadOnlySession, StringComparison.InvariantCultureIgnoreCase)) {
                if (WebOperationContext.Current.IncomingRequest.UriTemplateMatch != null) {
                    Uri serviceUri = HttpContext.Current.Session["ServiceUri"] as Uri;
                    Uri requestUri = null;

                    UriTemplateMatch match = WebOperationContext.Current.IncomingRequest.UriTemplateMatch;

                    if (serviceUri == null) {
                        // create a service Uri that represent the current session
                        UriBuilder serviceUriBuilder = new UriBuilder(match.BaseUri);
                        serviceUriBuilder.Path = "(S(" + HttpContext.Current.Session.SessionID + "))" + serviceUriBuilder.Path;

                        serviceUri = serviceUriBuilder.Uri;
                        HttpContext.Current.Session["ServiceUri"] = serviceUri;
                    }

                    if (serviceUri != null) {
                        OperationContext.Current.IncomingMessageProperties["MicrosoftDataServicesRootUri"] = serviceUri;

                        UriBuilder requestUriBuilder = new UriBuilder(match.RequestUri);
                        requestUriBuilder.Path = "(S(" + HttpContext.Current.Session.SessionID + "))" + requestUriBuilder.Path;
                        // because we have overwritten the Root URI, we need to make sure the request URI shares the same host
                        // (sometimes we have request URI resolving to a different host, if there are firewall re-directs
                        requestUriBuilder.Host = serviceUri.Host;

                        requestUri = requestUriBuilder.Uri;
                        OperationContext.Current.IncomingMessageProperties["MicrosoftDataServicesRequestUri"] = requestUri;
                    }
                }
            }
        }

  //      [ChangeInterceptor("Microformats")]
       // [ChangeInterceptor("Tag")]
        [ChangeInterceptor(Constants.ODATA_ENTITY_USER)]
        public void OnUpdateOperation(object source, UpdateOperations action) {
            if (HttpContext.Current != null && HttpContext.Current.Session != null) {
                String sessionId = HttpContext.Current.Session.SessionID;
                if (sessionId != ODataWebSessionIdManager.ODataReadOnlySession) {
                    return;
                }
            }

            throw new DataServiceException(403, "You are connected to a read-only data session. Update operations are not permitted for your session");
        }

        /// <summary>
        /// GetService: make sure we use our update provider
        /// </summary>
        /// <param name="serviceType">The type of the service to get</param>
        /// <returns>The service provider instance</returns>
        public override object GetService(Type serviceType) {
            return base.GetService(serviceType);
        }

        /// <summary>
        /// CreateDataSource - generating new data context if one does not already exist for the current session
        /// </summary>
        /// <returns>a new instance of DSPContext</returns>
        protected override DSPContext CreateDataSource() {
            if (HttpContext.Current == null) {
                // no context?
                return CreateDefaultData(this.Metadata);
            }

            string sid = HttpContext.Current.Session.SessionID;

            DSPContext dataSource;
            if (!_dsCache.TryGetValue(sid, out dataSource)) {
                if (_dsCache.Count > MaxCachedSessionItems) {
                    _dsCache.Clear();
                }

                dataSource = CreateDefaultData(this.Metadata);
                _dsCache.Add(sid, dataSource);
            }

            return dataSource;
        }

        /// <summary>
        /// Create default metadata
        /// </summary>
        /// <returns>An static instance of DSPMetadata</returns>
        protected override DSPMetadata CreateDSPMetadata() {
            if (_metadata == null) {
                CreateDefaultMetadata();
            }

            return _metadata;
        }



    }
}
